/**
 * @file process.c
 * @brief Implementation of process management functions
 *
 * This file contains implementations of various process-related operations 
 *
 * @author Fison
 * @date 2025-06-26
 */
/*system header*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <mcheck.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
/*user header*/
#include "../include/process.h"

#define _BSD_SOURCE

/*Shell command execution with output redirection principle*/
int shell_redirection(int argc,char *argv[]){
    if(argc < 4){
        printf("Usage:%s <command> <operation> <redirect file>", argv[0]);
        return -1;
    }
    int rc = fork();

    if(rc < 0)
    {
        fprintf(stderr,"fork faild\n");
        exit(1);
    }
    else if(rc == 0)
    {
        /*
          close stdout, the fd 1 will be released and become available
        
          fd 1 will be used as the fd of the newly opened file
          
          all of the output of this exectution will be written to the file whose fd is 1
        */
        close(STDOUT_FILENO);
        open(argv[3],O_CREAT|O_WRONLY|O_TRUNC,0644);
        char *myargs[3]; 
        myargs[0] = strdup(argv[1]);  
        myargs[1] = strdup(argv[2]); 
        myargs[2] = NULL;          // marks end of array 
        execvp(myargs[0], myargs);  // runs word count 
    }
    else
    {
        wait(NULL);
    }

    return 0;
}

int process_mtrace(){
    // Set environment variable for malloc trace output
    setenv("MALLOC_TRACE", "trace.log", 1);
    printf("$MALLOC_TRACE = %s\n",getenv("MALLOC_TRACE"));

    // Start tracing memory allocations
    mtrace();

    // Allocate and use memory to demonstrate tracing
    char *ch = (char *)malloc(1024);
    strcpy(ch, "hello world !");
    int *num = (int *)malloc(sizeof(int) * 1024);
    num[1027] = 0;
    
    // Stop memory tracing
    muntrace();
    return 0;
}

int process_valgrind(){
    // Allocate memory (intentionally leaving leaks for valgrind detection)
    char *ch = (char *)malloc(1024);
    strcpy(ch, "hello world !");
    int *num = (int *)malloc(sizeof(int) * 1024);
    num[1027] = 0;
    printf("valgrind memcheck! \n");

    // Note: Memory is not freed to demonstrate valgrind's leak detection
    return 0;
}

int process_calloc(){
    // Allocate and zero-initialize array of 100 mem_calloc elements
    mem_calloc *info = (mem_calloc *)calloc(100,sizeof(mem_calloc));
    if(info == NULL){
        perror("calloc");
        return -1;
    }

    // Demonstrate zero-initialization by checking value before/after setting
    printf("info[99].pid before init: %d\n", info[99].pid);
    info[99].pid = 25; 
    printf("info[99].pid after init: %d\n", info[99].pid);
    
    // Free allocated memory
    free(info);
    return 0;
}

int process_realloc(){
    // Allocate initial memory block
    char *ptr = (char *)malloc(24);
    if(ptr == NULL){
        perror("malloc");
        return -1;
    }

    // Store data in initial block
    strcpy(ptr,"hello world !");

    // Resize memory block to larger size
    char *nptr = (char *)realloc(ptr, 1000);
    if(nptr == NULL){
        perror("realloc");
        free(ptr);
        return -1;
    }

    // Update pointer and verify data was preserved
    ptr = nptr;
    printf("the value pointed by ptr is : %s\n", ptr);

    // Free allocated memory
    free(ptr);
    return 0;
}

int process_fork(){
    int a = 100;
    char b[1024] = "hello world!";

    pid_t proc = fork();
    if(proc == -1){
        perror("fork");
        return -1;
    }
    else if(proc == 0){
        a = 3;
        strcpy(b, "I am child process!");
        printf("a = %d,\n b = %s\n", a, b);
        return 0;
    }
    else{
        printf("I am parent process!\n");
        printf("a = %d,\n b = %s\n", a, b);
    } 

    return 0;
}


int process_fork_file_share(int argc, char *argv[]){
    if(argc < 2){
        printf("Usage:%s <file_name>\n", argv[0]);
        return -1;
    }

    // Open file with read-write permissions, create if doesn't exist
    int fd = open(argv[1], O_RDWR | O_CREAT, 0644);
    if (fd == -1){
        perror("open");
        return -1;
    }

    // Create child process
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        close(fd);
        return -1;
    } 
    
    if(pid == 0) {
        // Verify child process inherits file attributes from parent process 
        if(fcntl(fd, F_GETFL) & O_RDWR) {
            printf("File is opened with O_RDWR\n");
        }
        // Child process writes to file
        char str[] = "<this is child>";
        if(write(fd, str, strlen(str)) == -1){
            perror("Child write");
        };
    }
    
    if(pid > 0){
        sleep(1);  // Wait for child to complete writing
        lseek(fd, 0, SEEK_SET);  // Reset file pointer to beginning
        // Parent process reads from file after child writes
        char buf[1024];    
        if(read(fd, buf, 1024) == -1){
            perror("read");
            close(fd);
            return -1;
        }
        printf("parent read from file %s :\n %s\n", argv[1], buf);
    }
    
    close(fd);  // Close file descriptor
    return 0;
}

sig_atomic_t p_sync = 0;  // Atomic flag for process synchronization

void func_sig_handler(){
    p_sync = 1;  // Signal handler sets sync flag
}

int process_signal_sync(){
    signal(SIGUSR1, func_sig_handler);  // Register signal handler

    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }

    if(pid == 0){
        // Child waits for signal
        while(!p_sync){
            pause();  // Sleep until signal received
        }
        printf("child process executing!\n");
    }

    if(pid > 0){
        printf("parent process preparing!\n");
        sleep(1);  // Give child time to setup
        kill(pid, SIGUSR1);  // Send signal to child
    }

    return 0;
}

// Function to be called at exit
void func_atexit_01(){
    printf("func_atexit 01 is called\n");
}

// Function to be called at exit
void func_atexit_02(){
    printf("func_atexit 02 is called\n");
}

// Register exit handler and demonstrate execution order
int process_atexit(){
    // Register exit handler function
    if(atexit(func_atexit_01) != 0){
        printf("atexit error\n");
    };

    if(atexit(func_atexit_02) != 0){
        printf("atexit error\n");
    };
    
    // Main function execution
    printf("hello, this is the main function\n");
    return 0;
}

void func_onexit_01(int status, void *arg){
    printf("onexit func 01 is called\n");
    printf("status = %d, arg = %s\n", status, (char *)arg);
}

void func_onexit_02(int status, void *arg){
    printf("onexit func 02 is called\n");
    printf("status = %d, arg = %s\n", status, (char *)arg);
}

int process_onexit(){
    if(on_exit(func_onexit_01, "first") != 0)
        printf("onexit error\n");

    if(on_exit(func_onexit_02, "second") != 0)
        printf("onexit error\n");

    printf("hello, this is the main function\n");
    return 0;
}

int process_stdio_buff(){
    printf("hello world\n");

    write(STDOUT_FILENO, "Fison\n", 6);

    if(fork() == -1){
        perror("fork");
        return -1;
    }

    return 0;
}

int process_wait(int argc, char *argv[]){
    // Check if correct number of arguments is provided
    if(argc < 2){
        printf("Usage:%s <number to fork>\n", argv[0]);
        return -1;
    }

    pid_t pid;
    // Fork multiple child processes based on input argument
    for(int i = 0; i < atoi(argv[1]); i++){
        pid = fork();
        if(pid == -1){
            perror("fork");
            return -1;
        }

        // Child process execution
        if(pid == 0){
            printf("child %ld is created\n", (long int)getpid());
            sleep(1);  // Simulate work
            exit(EXIT_SUCCESS);  // Exit successfully
        }
    }

    // Parent process waits for all children to terminate
    pid_t child;
    while(1){
        child = wait(NULL);  // Wait for any child to terminate
        if(child == -1){
            if(errno == ECHILD){  // No more children left
                printf("all child terminated\n");
                break;
            } 
            else{  // Other error occurred
                printf("wait error\n");
                continue;
            }
        }
        printf("child %ld terminated successfully\n", (long int)child);
    }

    return 0;
}

int process_waitpid(){
    // Create child process
    pid_t pid = fork();
    if(pid == -1){
        perror("fork");
        return -1;
    }

    // Child process execution
    if(pid == 0){
        printf("this is child\n");
        exit(EXIT_SUCCESS);
    }
    
    // Parent process waits for child with status checking
    int status;
    pid_t w_child = waitpid(pid, &status, WUNTRACED); 
    if( w_child == -1){
        perror("waitpid");
        return -1;
    }
    else{
        printf("child %ld terminted\n", (long int)w_child);
    }

    // Check termination status
    if(WIFEXITED(status))
        printf("child termined normally\n");

    if(WIFSIGNALED(status))
        printf("child terminated by signal\n");

    return 0;
}

int process_exec(int argc,char *argv[]){
    if(argc < 3){
        printf("Usage: %s <executablefile> <arguements>", argv[0]);
        return -1;
    }

    char *envp[] = {"PATH=/bin:/usr/bin:/usr/local/bin", NULL};
    if (execve(argv[1], &argv[1], envp) == -1){
        perror("execve");
        return -1;
    }

    // This line will never be reached if execve succeeds
    printf("After execve\n");
    return 0;
}